5.ExoPlayer源码分析之PlayerView
前面两篇文章大体看了一下ExoPlayer.prepare()部分,接下来,我们要看一下UI部分的封装,PlayerView:
xml声明:
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/mPlayerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
private lateinit var mPlayer: SimpleExoPlayer
private fun initView(context: Context) {
val view = View.inflate(context, R.layout.video_view, this)
mPlayer = ExoPlayerFactory.newSimpleInstance(context)
// 通过PlayerView.setPlayer方法来将ExoPlayer绑定到View上
mPlayerView.player = mPlayer
}
public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider {
// 构造函数
public PlayerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater.from(context).inflate(playerLayoutId, this);
componentListener = new ComponentListener();
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
// Content frame.
contentFrame = findViewById(R.id.exo_content_frame);
if (contentFrame != null) {
setResizeModeRaw(contentFrame, resizeMode);
}
// Shutter view.
shutterView = findViewById(R.id.exo_shutter);
if (shutterView != null && shutterColorSet) {
shutterView.setBackgroundColor(shutterColor);
}
// 看对应的SurfaceView的类型,创建SurfaceView或TextureView
if (contentFrame != null && surfaceType != SURFACE_TYPE_NONE) {
ViewGroup.LayoutParams params =
new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
switch (surfaceType) {
case SURFACE_TYPE_TEXTURE_VIEW:
surfaceView = new TextureView(context);
break;
case SURFACE_TYPE_MONO360_VIEW:
SphericalSurfaceView sphericalSurfaceView = new SphericalSurfaceView(context);
sphericalSurfaceView.setSingleTapListener(componentListener);
surfaceView = sphericalSurfaceView;
break;
default:
surfaceView = new SurfaceView(context);
break;
}
surfaceView.setLayoutParams(params);
contentFrame.addView(surfaceView, 0);
} else {
surfaceView = null;
}
// Ad overlay frame layout.
adOverlayFrameLayout = findViewById(R.id.exo_ad_overlay);
// Overlay frame layout.
overlayFrameLayout = findViewById(R.id.exo_overlay);
// Artwork view.
artworkView = findViewById(R.id.exo_artwork);
this.useArtwork = useArtwork && artworkView != null;
if (defaultArtworkId != 0) {
defaultArtwork = ContextCompat.getDrawable(getContext(), defaultArtworkId);
}
// Subtitle view.
subtitleView = findViewById(R.id.exo_subtitles);
if (subtitleView != null) {
subtitleView.setUserDefaultStyle();
subtitleView.setUserDefaultTextSize();
}
// Buffering view.
bufferingView = findViewById(R.id.exo_buffering);
if (bufferingView != null) {
bufferingView.setVisibility(View.GONE);
}
this.showBuffering = showBuffering;
// Error message view.
errorMessageView = findViewById(R.id.exo_error_message);
if (errorMessageView != null) {
errorMessageView.setVisibility(View.GONE);
}
// Playback control view.
PlayerControlView customController = findViewById(R.id.exo_controller);
View controllerPlaceholder = findViewById(R.id.exo_controller_placeholder);
if (customController != null) {
this.controller = customController;
} else if (controllerPlaceholder != null) {
// Propagate attrs as playbackAttrs so that PlayerControlView's custom attributes are
// transferred, but standard attributes (e.g. background) are not.
this.controller = new PlayerControlView(context, null, 0, attrs);
controller.setId(R.id.exo_controller);
controller.setLayoutParams(controllerPlaceholder.getLayoutParams());
ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent());
int controllerIndex = parent.indexOfChild(controllerPlaceholder);
parent.removeView(controllerPlaceholder);
parent.addView(controller, controllerIndex);
} else {
this.controller = null;
}
this.controllerShowTimeoutMs = controller != null ? controllerShowTimeoutMs : 0;
this.controllerHideOnTouch = controllerHideOnTouch;
this.controllerAutoShow = controllerAutoShow;
this.controllerHideDuringAds = controllerHideDuringAds;
this.useController = useController && controller != null;
hideController();
}
public void setPlayer(@Nullable Player player) {
if (this.player == player) {
return;
}
if (this.player != null) {
this.player.removeListener(componentListener);
Player.TextComponent oldTextComponent = this.player.getTextComponent();
if (oldTextComponent != null) {
oldTextComponent.removeTextOutput(componentListener);
}
}
this.player = player;
// 把Player设置给PlayerControlView
if (useController) {
controller.setPlayer(player);
}
if (subtitleView != null) {
subtitleView.setCues(null);
}
updateBuffering();
updateErrorMessage();
updateForCurrentTrackSelections(/* isNewPlayer= */ true);
if (player != null) {
// 注意下这里
Player.VideoComponent newVideoComponent = player.getVideoComponent();
if (newVideoComponent != null) {
if (surfaceView instanceof TextureView) {
newVideoComponent.setVideoTextureView((TextureView) surfaceView);
} else if (surfaceView instanceof SphericalSurfaceView) {
((SphericalSurfaceView) surfaceView).setVideoComponent(newVideoComponent);
} else if (surfaceView instanceof SurfaceView) {
newVideoComponent.setVideoSurfaceView((SurfaceView) surfaceView);
}
newVideoComponent.addVideoListener(componentListener);
}
Player.TextComponent newTextComponent = player.getTextComponent();
if (newTextComponent != null) {
newTextComponent.addTextOutput(componentListener);
}
player.addListener(componentListener);
maybeShowController(false);
} else {
hideController();
}
}
}
构造函数里面只是创建了SurfaceView,然后添加了一些其他的View,例如PlayerControlView。
看到这里有个问题,PlayerView里面会去创建SurfaceView,那它是怎么传递到ExoPlayerl里面的?
// 注意下这里
Player.VideoComponent newVideoComponent = player.getVideoComponent();
if (newVideoComponent != null) {
if (surfaceView instanceof TextureView) {
newVideoComponent.setVideoTextureView((TextureView) surfaceView);
} else if (surfaceView instanceof SphericalSurfaceView) {
((SphericalSurfaceView) surfaceView).setVideoComponent(newVideoComponent);
} else if (surfaceView instanceof SurfaceView) {
newVideoComponent.setVideoSurfaceView((SurfaceView) surfaceView);
}
newVideoComponent.addVideoListener(componentListener);
}
newVideoComponent.setVideoTextureView里面会调用SimpleExoPlayer中的setVideoSurfaceView:
@Override
public void setVideoSurfaceView(SurfaceView surfaceView) {
setVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder());
}
@Override
public void setVideoSurfaceHolder(SurfaceHolder surfaceHolder) {
verifyApplicationThread();
removeSurfaceCallbacks();
this.surfaceHolder = surfaceHolder;
if (surfaceHolder == null) {
setVideoSurfaceInternal(null, false);
maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0);
} else {
surfaceHolder.addCallback(componentListener);
Surface surface = surfaceHolder.getSurface();
if (surface != null && surface.isValid()) {
setVideoSurfaceInternal(surface, /* ownsSurface= */ false);
Rect surfaceSize = surfaceHolder.getSurfaceFrame();
maybeNotifySurfaceSizeChanged(surfaceSize.width(), surfaceSize.height());
} else {
setVideoSurfaceInternal(/* surface= */ null, /* ownsSurface= */ false);
maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0);
}
}
}
然后会调用setVideoSurfaceInternal:
private void setVideoSurfaceInternal(@Nullable Surface surface, boolean ownsSurface) {
// Note: We don't turn this method into a no-op if the surface is being replaced with itself
// so as to ensure onRenderedFirstFrame callbacks are still called in this case.
List<PlayerMessage> messages = new ArrayList<>();
// 遍历所有renderer,如果是视频类型
for (Renderer renderer : renderers) {
if (renderer.getTrackType() == C.TRACK_TYPE_VIDEO) {
messages.add(
// 发送msg给MediaCodecVideoRenderer
player.createMessage(renderer).setType(C.MSG_SET_SURFACE).setPayload(surface).send());
}
}
// 如果之前有surface,把之前的释放掉
if (this.surface != null && this.surface != surface) {
// We're replacing a surface. Block to ensure that it's not accessed after the method returns.
try {
for (PlayerMessage message : messages) {
message.blockUntilDelivered();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// If we created the previous surface, we are responsible for releasing it.
if (this.ownsSurface) {
this.surface.release();
}
}
this.surface = surface;
this.ownsSurface = ownsSurface;
}
接下来继续看一下MediaCodecVideoRenderer里面处理Message的部分:
@Override
public void handleMessage(int messageType, @Nullable Object message) throws ExoPlaybackException {
if (messageType == C.MSG_SET_SURFACE) {
// 直接调用setSurface()方法
setSurface((Surface) message);
} else if (messageType == C.MSG_SET_SCALING_MODE) {
scalingMode = (Integer) message;
MediaCodec codec = getCodec();
if (codec != null) {
codec.setVideoScalingMode(scalingMode);
}
} else if (messageType == C.MSG_SET_VIDEO_FRAME_METADATA_LISTENER) {
frameMetadataListener = (VideoFrameMetadataListener) message;
} else {
super.handleMessage(messageType, message);
}
}
接着看一下setSurface()方法:
private void setSurface(Surface surface) throws ExoPlaybackException {
if (surface == null) {
// Use a dummy surface if possible.
if (dummySurface != null) {
surface = dummySurface;
} else {
MediaCodecInfo codecInfo = getCodecInfo();
if (codecInfo != null && shouldUseDummySurface(codecInfo)) {
dummySurface = DummySurface.newInstanceV17(context, codecInfo.secure);
surface = dummySurface;
}
}
}
// We only need to update the codec if the surface has changed.
if (this.surface != surface) {
this.surface = surface;
@State int state = getState();
MediaCodec codec = getCodec();
if (codec != null) {
if (Util.SDK_INT >= 23 && surface != null && !codecNeedsSetOutputSurfaceWorkaround) {
setOutputSurfaceV23(codec, surface);
} else {
releaseCodec();
maybeInitCodec();
}
}
if (surface != null && surface != dummySurface) {
// If we know the video size, report it again immediately.
maybeRenotifyVideoSizeChanged();
// We haven't rendered to the new surface yet.
clearRenderedFirstFrame();
if (state == STATE_STARTED) {
setJoiningDeadlineMs();
}
} else {
// The surface has been removed.
clearReportedVideoSize();
clearRenderedFirstFrame();
}
} else if (surface != null && surface != dummySurface) {
// The surface is set and unchanged. If we know the video size and/or have already rendered to
// the surface, report these again immediately.
maybeRenotifyVideoSizeChanged();
maybeRenotifyRenderedFirstFrame();
}
}
上一篇: 4. ExoPlayer源码分析之prepare序列图
- 邮箱 :[email protected]
- Good Luck!